package com.sdgs.paas.sdk.core.web;

import static com.sdgs.paas.sdk.core.utils.DateUtils.getCurrentTimeStamp;
import static com.sdgs.paas.sdk.core.web.loadbalance.LabelAndWeightMetadataRule.META_DATA_KEY_LABEL_OR;

import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;

import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableDefault;
import com.sdgs.paas.sdk.core.log.PaasLogger;

/**
 * SDK HTTP头处理类
 * 
 * 
 * 请求头
 * <p>
 * 
 * VerNo 版本号 协议版本
 * <p>
 * testFlag 测试标识
 * <p>
 * TraceId 系统唯一流水 由pinpoint生成
 * <p>
 * PspanId 系统模块流水 由pinpoint生成
 * <p>
 * SpanId 系统模块流水 由pinpoint生成
 * <p>
 * ReqTimestamp 系统时间戳
 * <p>
 * ReqDockerIp 请求方DockerIP
 * <p>
 * ReqChannelId 请求方渠道标识
 * <p>
 * ReqUserId 请求用户标识
 * <p>
 * ReqClientIP 请求方IP地址
 * <p>
 * ReqType 请求类型 0-正常 1-重试
 * <p>
 * ReqBizNum 请求方流水
 * <p>
 * 
 * 响应头
 * <p>
 * 
 * VerNo 版本号 协议版本
 * <p>
 * testFlag 测试标识
 * <p>
 * TraceId 系统唯一流水 由pinpoint生成
 * <p>
 * PspanId 上级系统模块流水 由pinpoint生成
 * <p>
 * SpanId 系统模块流水 由pinpoint生成
 * <p>
 * ReqTimestamp 系统时间戳
 * <p>
 * ReqDockerIp 请求方DockerIP
 * <p>
 * ReqChannelId 请求方渠道标识
 * <p>
 * ReqUserId 请求用户标识
 * <p>
 * ReqClientIP 请求方IP地址
 * <p>
 * ReqType 请求类型 0-正常 1-重试
 * <p>
 * ReqBizNum 请求方流水
 * <p>
 * RepTimestamp 响应方时间
 * <p>
 * RepDockerIp 响应方DockerIP
 * <p>
 * RepServUnitName 响应方服务单元名称
 * <p>
 * RepServUnitVersion 响应方服务单元版本
 * <p>
 * 
 * @author wjj
 * 
 */
public class PaasHttpHeader {
	@Autowired
	PaasHttpProperties paasHttpProperties;

	// 协议版本号 固定值 配置|| 从请求头上复制 ，以下注释按照此规则，本实例作为客户端的取数方法||本实例作为服务端的取数方法
	public static final String VerNo = "VerNo";
	// 测试标识，0-正常 1-测试， 配置||从请求头上复制
	public static final String TestFlag = "TestFlag";
	// 系统唯一流水， 由pinpoint生成||从请求头上复制
	public static final String TraceId = "TraceId";
	// 系统模块流水， 由pinpoint生成||从请求头上复制
	public static final String PspanId = "PspanId";
	// 系统模块流水， 由pinpoint生成||从请求头上复制
	public static final String SpanId = "SpanId";
	// 系统时间戳，System.currentTimeMillis()||从请求头上复制
	public static final String ReqTimestamp = "ReqTimestamp";
	// 请求方DockerIP，本实例ip||从请求头上复制
	public static final String ReqDockerIp = "ReqDockerIp";
	// 请求方渠道标识，从头上取，透传到下游||从请求头上复制
	public static final String ReqChannelId = "ReqChannelId";
	// 请求用户标识，从头上取，透传到下游||从请求头上复制
	public static final String ReqUserId = "ReqUserId";
	// 请求方IP地址，最外层客户端ip，从头上取，透传到下游||从请求头上复制
	public static final String ReqClientIP = "ReqClientIP";
	// 请求类型 0-正常 1-重试 配置||从请求头上复制
	public static final String ReqType = "ReqType";
	// 同一个进程里的nanotime都是基于某一个固定时间点，所以一个进程内是唯一的
	// 一个进程内两个不同的对象也有可能会hashcode相同，因为可能的不同对象的数量经常比可能的哈希吗的数量 (2^32)更大。
	// 所以再加一个nano时间，在nano的粒度内，产生不同的request的可能比较小(new也是要消耗时间的)，产生相同hashcode的request的可能更小
	// 请求方流水，${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}+System.nanoTime+request对象hashcode||从请求头上复制
	public static final String ReqBizNum = "ReqBizNum";

	// public static final String ReqPort = "ReqPort";

	// 其他实例请求本实例，从http头上获取这些信息放到mdc，然后日志输出
	private final String[] ReqHeaders = { VerNo, TestFlag, TraceId, PspanId, SpanId, ReqTimestamp, ReqDockerIp,
			ReqChannelId, ReqUserId, ReqClientIP, ReqType, ReqBizNum, META_DATA_KEY_LABEL_OR

	};

	// 其他实例请求本实例，把req传过来的这些头，原封不动的在response里返回，req的这些东西返回去有啥用？？？
	private final String[] CopyReqHeaders = { TestFlag, TraceId, PspanId, SpanId, ReqTimestamp, ReqDockerIp,
			ReqChannelId, ReqUserId, ReqClientIP, ReqType, ReqBizNum, META_DATA_KEY_LABEL_OR

	};

	// 本实例请求其他实例，构建这些http头
	private final String[] BuildReqHeaders = { ReqChannelId, ReqUserId, ReqClientIP, META_DATA_KEY_LABEL_OR

	};
	// 响应方时间，System.currentTimeMillis()||从请求头上复制
	public static final String RepTimestamp = "RepTimestamp";
	// 响应方DockerIP，${spring.cloud.client.ipAddress})||从请求头上复制
	public static final String RepDockerIp = "RepDockerIp";
	// 响应方服务单元名称，${spring.application.name}||从请求头上复制
	public static final String RepServUnitName = "RepServUnitName";
	// 响应方服务单元版本，${spring.cloud.config.profile}||从请求头上复制
	public static final String RepServUnitVersion = "RepServUnitVersion";

	// private static final String[] RepHeaders = { VerNo, TestFlag, TraceId,
	// PspanId, SpanId, ReqTimestamp, ReqDockerIp,
	// ReqChannelId, ReqUserId, ReqClientIP, ReqType, ReqBizNum, RepTimestamp,
	// RepDockerIp, RepServUnitName,
	// RepServUnitVersion };

	private final HystrixRequestVariableDefault<Map<String, String>> label = new HystrixRequestVariableDefault<>();

	/**
	 * 从原始请求内获取http header
	 * 
	 * @param headname
	 * @return
	 */
	public String getFromOrigHeader(String headname) {
		return getHystrixVar().get(headname);
	}

	/**
	 * 初始化HystrixRequestContext,并在MDC中放入HTTP头，以便后续日志输出
	 * 
	 * @param request
	 */
	public void initHystrixRequestContext(HttpServletRequest request) {

		Map<String, String> headers = getHystrixVar();

		Arrays.asList(ReqHeaders).stream().forEach(e -> {
			String headval = request.getHeader(e);
			if (headval != null) {
				headers.put(e, headval);
			}

		});

		headers.entrySet().stream().forEach(e -> MDC.put(e.getKey(), e.getValue()));

		// MDC.put(ServPort, paasHttpProperties.getSiPort());
		// MDC.put(RepServUnitName, paasHttpProperties.getRepServUnitName());
		// MDC.put(RepServUnitVersion,
		// paasHttpProperties.getRepServUnitVersion());

	}

	/**
	 * 初始化HystrixRequestContext，返回httpheader
	 */
	private Map<String, String> getHystrixVar() {

		if (!HystrixRequestContext.isCurrentThreadInitialized()) {
			HystrixRequestContext.initializeContext();
		}
		Map<String, String> headers = label.get();
		if (headers == null) {
			headers = new ConcurrentHashMap<>();
			label.set(headers);
		}

		return headers;
	}

	/**
	 * 初始化HystrixRequestContext,并放入httpheader
	 * 
	 * @param request
	 */
	public void initHystrixRequestContext(HttpServletRequest request, String headname, String headval) {

		Map<String, String> headers = getHystrixVar();
		if (headval != null) {
			headers.put(headname, headval);
		}

	}

	/**
	 * 关闭HystrixRequestContext
	 */
	public void shutdownHystrixRequestContext() {
		if (HystrixRequestContext.isCurrentThreadInitialized()) {
			HystrixRequestContext.getContextForCurrentThread().shutdown();
		}
	}

	/**
	 * 把request的http header复制到response http header
	 * 
	 * @param response
	 */
	public void copyReqHeader(HttpServletResponse response) {
		for (String headname : CopyReqHeaders) {
			String headval = getFromOrigHeader(headname);
			if (headval != null) {
				response.setHeader(headname, headval);
			}

		}
	}

	/**
	 * 响应头处理
	 * 
	 * @param response
	 */
	public void setPaasRepHeader(HttpServletResponse response) {
		response.setHeader(VerNo, paasHttpProperties.getRepVerNo());
		copyReqHeader(response);

		response.setHeader(RepTimestamp, getCurrentTimeStamp());
		response.setHeader(RepDockerIp, paasHttpProperties.getSiIP());
		response.setHeader(RepServUnitName, paasHttpProperties.getRepServUnitName());
		response.setHeader(RepServUnitVersion, paasHttpProperties.getRepServUnitVersion());
	}

	/**
	 * 请求头处理
	 * 
	 * @param httpHeaders
	 */
	public void setPaasReqHeader(HttpHeaders httpHeaders, HttpRequest request) {
		httpHeaders.add(VerNo, paasHttpProperties.getReqVerNo());
		httpHeaders.add(TestFlag, paasHttpProperties.getReqTestFlag());
		httpHeaders.add(ReqType, paasHttpProperties.getReqType());
		httpHeaders.add(ReqTimestamp, getCurrentTimeStamp());
		httpHeaders.add(ReqDockerIp, paasHttpProperties.getSiIP());

		StringBuilder sb = new StringBuilder();
		String reqBizNum = sb.append(paasHttpProperties.getSiDesc()).append("|").append(System.nanoTime()).append("|")
				.append(request.hashCode()).toString();
		PaasLogger.info("ReqBizNum:{}", reqBizNum);
		httpHeaders.add(ReqBizNum, reqBizNum);

		// 内部直接发起的请求，没有外部的httpheader，跳过复制请求头的步骤
		if (getHystrixVar().size() > 0) {
			bulidReqHeader(httpHeaders, BuildReqHeaders);
		}

	}

	private void bulidReqHeader(HttpHeaders httpHeaders, String[] heads) {
		for (String headname : heads) {
			String headval = getFromOrigHeader(headname);
			if (headval != null) {
				httpHeaders.add(headname, getFromOrigHeader(headval));
			}

		}

	}

}